home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 9675 / 9675.xpi / chrome / content / simpletimer.js < prev   
Text File  |  2009-11-29  |  95KB  |  2,389 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Simple Timer.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * George Bradt.
  18.  *
  19.  * Portions created by the Initial Developer are Copyright (C) 2008
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  * George Bradt.
  24.  * Tsprajna, localization of app description.
  25.  *
  26.  * Credits:
  27.  *   clockBlue16.png, clockRed16.png - Mark James at http://www.famfamfam.com/lab/icons/silk/
  28.  *   clock32.png, clock48.png - David Vignoni at http://www.icon-king.com
  29.  *   textbox style (adapted) - Regis Caspar at http://caspar.regis.free.fr/timespinner/
  30.  *
  31.  * Alternatively, the contents of this file may be used under the terms of
  32.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  33.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  34.  * in which case the provisions of the GPL or the LGPL are applicable instead
  35.  * of those above. If you wish to allow use of your version of this file only
  36.  * under the terms of either the GPL or the LGPL, and not to allow others to
  37.  * use your version of this file under the terms of the MPL, indicate your
  38.  * decision by deleting the provisions above and replace them with the notice
  39.  * and other provisions required by the GPL or the LGPL. If you do not delete
  40.  * the provisions above, a recipient may use your version of this file under
  41.  * the terms of any one of the MPL, the GPL or the LGPL.
  42.  *
  43.  * ***** END LICENSE BLOCK ***** */
  44.  
  45. var SimpleTimer = {
  46.     prefs: null,
  47.     popupNotify: false,
  48.     audioNotify: false,
  49.     dialogNotify: false,
  50.     repeatAudio: 0,
  51.     repeatMax: 1,
  52.     audioFile: 0,
  53.     displayIn: 0,
  54.     onStartup: 0,
  55.     eventLogging: true,
  56.     eventLogPath: "",
  57.     deleteLogEntries: 0,
  58.     leftClick: 0,
  59.     disablePauseResume: false,
  60.     tooltip: 2,
  61.     clockType: 0,
  62.     showSecs: false,
  63.     clock24Hr: false,
  64.     dateFormat: 0,
  65.     countdownSeconds: 0,
  66.     countdownMinutes: 0,
  67.     countdownHours: 0,
  68.     countdownMilli: 0,
  69.     countdownRecurring: false,
  70.     countdowns: "",
  71.     favCountdowns: "",
  72.     notifyRecur: "",
  73.     countdownTable: [],
  74.     countdownTimer: {timeDisplay: "",
  75.                      recurring: false,
  76.                      description: "",
  77.                      timeMilli: 0,
  78.                      timeFinishedMilli: 0,
  79.                      paused: false
  80.                     },
  81.     notifyTable: [],
  82.     notification: {completed: false,
  83.                    recurring: false,
  84.                    timeDisplay: "",
  85.                    description: "",
  86.                    time24hr: "",
  87.                    timeMilli: 0,
  88.                    day: -1,
  89.                    url: ""
  90.                   },
  91.     currNotifyIndex: 0,
  92.     completedIndices: [],
  93.     currNotifyInterval: 0,
  94.     paused: false,
  95.     timerTooltipID: 0,
  96.     timerID: 0,
  97.     notifyID: 0,
  98.     repeatAudioId: 0,
  99.     repeatMaxId: 0,
  100.     startTime: null,
  101.     startBrowserTime: null,
  102.     elapsedSecs: 0,
  103.     elapsedMins: 0,
  104.     elapsedHours: 0,
  105.     elapsedMilli: 0,
  106.     remainingSecs: 0,
  107.     remainingMins: 0,
  108.     remainingHours: 0,
  109.     remainingMilli: 0,
  110.     clockInProgress: false,
  111.     countdownInProgress: false,
  112.     countupInProgress: false,
  113.     savedCountupTime: "",
  114.     barContainerElement: "",
  115.     barImageElement: "",
  116.     barLabelElement: "",
  117.     buttonOnToolbar: false,
  118.     customAudioFile: null,
  119.     customAudioPath: "",
  120.     audioObjectSupport: false,
  121.     audioObject: null,
  122.     usingAudioObject: false,
  123.     audioDuration: 0,
  124.  
  125.     // Initialize the extension.
  126.  
  127.     startup: function() {
  128.         var strbundle = document.getElementById("simtim-strings");
  129.  
  130.         try {
  131.             // Check if this browser supports audio object (>= ff 3.5).
  132.             this.audioObject = new Audio();
  133.             this.audioObjectSupport = true;
  134.  
  135.             // "pause" and "abort" listeners for debugging purposes.
  136.             this.audioObject.addEventListener("loadedmetadata", function(event) { SimpleTimer.audioLoadedMetaData(event); }, false);
  137.             this.audioObject.addEventListener("ended", function(event) { SimpleTimer.audioPlaybackEnded(event); }, false);
  138.             this.audioObject.addEventListener("pause", function(event) { SimpleTimer.audioPlaybackPause(event); }, false);
  139.             this.audioObject.addEventListener("abort", function(event) { SimpleTimer.audioPlaybackAbort(event); }, false);
  140.             this.audioObject.addEventListener("error", function(event) { SimpleTimer.audioPlaybackError(event); }, false);
  141.         }
  142.         catch(e) {
  143.             this.audioObjectSupport = false;
  144.         }
  145.  
  146.         try {
  147.             // Register to receive notification of windows opening (specifically "Customize Toolbar"),
  148.             // to keep an eye on whereabouts of toolbar button.
  149.             var observerService = Components.classes["@mozilla.org/observer-service;1"].
  150.                     getService(Components.interfaces.nsIObserverService);
  151.             observerService.addObserver(this, "domwindowopened", false);
  152.         }
  153.         catch(e) {
  154.             alert(strbundle.getString("alert.error.observer.service") +
  155.                   "\n" + e.message);
  156.         }
  157.  
  158.         try {
  159.             // Register to receive notifications of preference changes.
  160.             this.prefs = Components.classes["@mozilla.org/preferences-service;1"].
  161.                     getService(Components.interfaces.nsIPrefService).
  162.                     getBranch("extensions.simpletimer@grbradt.org.");
  163.             this.prefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
  164.             this.prefs.addObserver("", this, false);
  165.  
  166.             this.popupNotify =         this.prefs.getBoolPref("popupNotify");
  167.             this.audioNotify =         this.prefs.getBoolPref("audioNotify");
  168.             this.dialogNotify =        this.prefs.getBoolPref("dialogNotify");
  169.             this.repeatAudio =         this.prefs.getIntPref("repeatAudio");
  170.             this.repeatMax =           this.prefs.getIntPref("repeatMax");
  171.             this.audioFile =           this.prefs.getIntPref("audioFile");
  172.             this.displayIn =           this.prefs.getIntPref("displayIn");
  173.             this.onStartup =           this.prefs.getIntPref("onStartup");
  174.             this.eventLogging =        this.prefs.getBoolPref("eventLogging");
  175.             this.deleteLogEntries =    this.prefs.getIntPref("deleteLogEntries");
  176.             this.leftClick =           this.prefs.getIntPref("leftClick");
  177.             this.disablePauseResume =  this.prefs.getBoolPref("disablePauseResume");
  178.             this.tooltip =             this.prefs.getIntPref("tooltip");
  179.             this.clockType =           this.prefs.getIntPref("clockType");
  180.             this.showSecs =            this.prefs.getBoolPref("showSecs");
  181.             this.clock24Hr =           this.prefs.getBoolPref("clock24Hr");
  182.             this.dateFormat =          this.prefs.getIntPref("dateFormat");
  183.             this.countdownHours =      this.prefs.getIntPref("countdownHours");
  184.             this.countdownMinutes =    this.prefs.getIntPref("countdownMinutes");
  185.             this.countdownSeconds =    this.prefs.getIntPref("countdownSeconds");
  186.  
  187.             // May contain Unicode characters.
  188.             this.countdowns =  this.prefs.getComplexValue("countdowns",
  189.                         Components.interfaces.nsISupportsString).data;
  190.             this.favCountdowns =  this.prefs.getComplexValue("favCountdowns",
  191.                         Components.interfaces.nsISupportsString).data;
  192.             this.notifyRecur = this.prefs.getComplexValue("notifyRecur",
  193.                                Components.interfaces.nsISupportsString).data;
  194.  
  195.             // As of ver 1.7, userWavPath a misnomer.
  196.             if ( this.prefs.prefHasUserValue("userWavPath") ) {
  197.                 this.customAudioFile =  this.prefs.getComplexValue("userWavPath",
  198.                         Components.interfaces.nsILocalFile);
  199.  
  200.                 try {
  201.                     if ( this.customAudioFile.isFile() ) {
  202.                         this.customAudioPath = this.customAudioFile.path;
  203.  
  204.                         if ( this.audioObjectSupport &&
  205.                              this.customAudioPath.substr(this.customAudioPath.length - 4) === ".ogg") {
  206.                             this.usingAudioObject = true;
  207.                             this.audioObject.src = "file:///" + this.customAudioPath;
  208.  
  209.                             // Seems to fail silently without proper file header.
  210.                             this.audioObject.load();
  211.                         }
  212.                         else {
  213.                             this.usingAudioObject = false;
  214.                         }
  215.                     }
  216.                 }
  217.                 catch(e) {
  218.                     alert(strbundle.getString("alert.error.invalid.audio.file") +
  219.                             "\n" + e.name);
  220.                 }
  221.             }
  222.             else {
  223.                 this.usingAudioObject = false;
  224.             }
  225.  
  226.             if ( this.prefs.prefHasUserValue("eventLogPath") ) {
  227.                 this.eventLogPath = this.prefs.getComplexValue("eventLogPath",
  228.                         Components.interfaces.nsILocalFile).path;
  229.             }
  230.         }
  231.         catch(e) {
  232.             alert(strbundle.getString("alert.error.get.prefs") +
  233.                   "\n" + e.message);
  234.         }
  235.  
  236.         // Time the browser session.
  237.         this.startBrowserTime = new Date();
  238.  
  239.         // Create default logging file.
  240.         if ( !this.eventLogPath ) {
  241.             SimpleTimerEventLog.createDefaultLogFile();
  242.         }
  243.  
  244.         // Build a notifications table.
  245.         if ( this.notifyRecur ) {
  246.             SimpleTimerNotify.buildNotifyTableFromPref(this.notifyRecur);
  247.             this.getNotifyTable();
  248.         }
  249.  
  250.         // Is toolbar button in palette or on bar?
  251.         // This is checked again whenever the "Customize Toolbar" window is closed.
  252.         var toolbarItem = document.getElementById("simtim-toolbarItem");
  253.         this.buttonOnToolbar = ( toolbarItem ) ? true : false;
  254.  
  255.         // Check which bar we are using.
  256.         if ( this.displayIn === 0 ) {
  257.             // Statusbar.
  258.             this.setStatusbar();
  259.             this.setIconImage(this.barImageElement);
  260.         }
  261.         else if ( this.displayIn === 1 ) {
  262.             // Toolbar.
  263.             if ( this.buttonOnToolbar ) {
  264.                 this.setToolbar();
  265.                 this.setIconImage(this.barImageElement);
  266.             }
  267.             else {
  268.                 // Warn user to move button from palette to toolbar.
  269.                 document.getElementById("simtim-statpanelTimer").hidden = true;
  270.                 var params = { displayItems: null, msg: strbundle.getString("alert.warning.toolbar.button") };
  271.                 SimpleTimerSliderAlert.addMessageToQueue(params);
  272.             }
  273.         }
  274.         else {
  275.             // Both.
  276.             this.setStatusbar();
  277.  
  278.             if ( this.buttonOnToolbar ) {
  279.                 this.setToolbar();
  280.                 document.getElementById("simtim-toolbarItem").hidden = false;
  281.                 document.getElementById("simtim-statpanelTimer").hidden = false;
  282.             }
  283.             else {
  284.                 var params = { displayItems: null, msg: strbundle.getString("alert.warning.toolbar.button") };
  285.                 SimpleTimerSliderAlert.addMessageToQueue(params);
  286.             }
  287.  
  288.             this.setIconImage("both");
  289.         }
  290.  
  291.         // If there are notifications, start timing.
  292.         if ( this.notifyRecur ) {
  293.             this.setNotifyInterval();
  294.         }
  295.  
  296.         // Build a countdowns table.
  297.         if ( this.countdowns ) {
  298.             // Incomplete timers from previous session.
  299.             SimpleTimerCountdown.buildCountdownTableFromPref(this.countdowns);
  300.             this.getCountdownTable();
  301.  
  302.             if ( this.countdownTable.length > 0 ) {
  303.                 // Continue timers. Supercedes auto functions.
  304.                 this.setCountdownTimer();
  305.                 return;
  306.             }
  307.             else {
  308.                 // Timers from previous session have all expired. Update countdowns pref.
  309.                 this.setCountdownTable();
  310.                 SimpleTimerCountdown.updateCountdownPref();
  311.             }
  312.         }
  313.  
  314.         // Check if countup in progress in other browser windows.
  315.         if ( this.countupInOtherBrowserWindow() ) {
  316.             // Do likewise. Supercedes auto functions.
  317.             return;
  318.         }
  319.  
  320.         // Check for auto funtions.
  321.         switch ( this.onStartup ) {
  322.             case 0:
  323.                 break;
  324.  
  325.             case 1:
  326.                 this.clock();
  327.                 break;
  328.  
  329.             case 2:
  330.                 this.countUp();
  331.                 break;
  332.  
  333.             default:
  334.                 alert(strbundle.getString("alert.error.invalid.startup"));
  335.         }
  336.     },
  337.  
  338.     // Clean up after ourselves.
  339.  
  340.     shutdown: function() {
  341.         this.prefs.removeObserver("", this);
  342.  
  343.         var observerService = Components.classes["@mozilla.org/observer-service;1"].
  344.                 getService(Components.interfaces.nsIObserverService);
  345.         observerService.removeObserver(this, "domwindowopened");
  346.  
  347.         if ( this.audioObjectSupport ) {
  348.             this.audioObject.removeEventListener("loadedmetadata", function(event) { SimpleTimer.audioPlaybackEnded(event); }, false);
  349.             this.audioObject.removeEventListener("ended", function(event) { SimpleTimer.audioPlaybackEnded(event); }, false);
  350.             this.audioObject.removeEventListener("pause", function(event) { SimpleTimer.audioPlaybackPause(event); }, false);
  351.             this.audioObject.removeEventListener("abort", function(event) { SimpleTimer.audioPlaybackAbort(event); }, false);
  352.             this.audioObject.removeEventListener("error", function(event) { SimpleTimer.audioPlaybackError(event); }, false);
  353.         }
  354.     },
  355.  
  356.     // Called when events occur on the preferences, or a window is opened/closed..
  357.  
  358.     observe: function(subject, topic, data) {
  359.         var strbundle = document.getElementById("simtim-strings");
  360.  
  361.         if ( topic === "domwindowopened" ) {
  362.             // Window hasn't loaded yet, so we don't know which window this is.
  363.             // Only interested in "Customize Toolbar" window, so add a load listener.
  364.             subject.addEventListener("load", function(event) { SimpleTimer.windowLoaded(event); }, false);
  365.         }
  366.         else if ( topic === "domwindowclosed" ) {
  367.             // Only called when "Customize Toolbar" window is closed.
  368.             var onToolbarWhenOpened = this.buttonOnToolbar;
  369.  
  370.             this.buttonOnToolbar = ( document.getElementById("simtim-toolbarItem") ) ?
  371.                     true : false;
  372.  
  373.             var onToolbarWhenClosed = this.buttonOnToolbar;
  374.  
  375.             if ( !onToolbarWhenOpened && onToolbarWhenClosed ) {
  376.                 // User just dragged button to toolbar...
  377.                 if ( this.displayIn > 0 ) {
  378.                     // ...and it's in play.
  379.                     this.setToolbar();
  380.  
  381.                     if ( this.displayIn === 2 ) {
  382.                         document.getElementById("simtim-statpanelTimer").hidden = false;
  383.                     }
  384.  
  385.                     if ( this.clockInProgress || this.countdownInProgress || this.countupInProgress ) {
  386.                         this.changeBarMode("wake");
  387.                     }
  388.                     else {
  389.                         this.changeBarMode("sleep");
  390.                     }
  391.                 }
  392.                 else {
  393.                     // ...but display is set to statusbar.
  394.                     // Warn user to change display option.
  395.                     alert(strbundle.getString("alert.warning.toolbar.option"));
  396.                 }
  397.             }
  398.  
  399.             var observerService = Components.classes["@mozilla.org/observer-service;1"].
  400.                     getService(Components.interfaces.nsIObserverService);
  401.             observerService.removeObserver(this, "domwindowclosed");
  402.         }
  403.         else if ( topic === "nsPref:changed" ) {
  404.             switch ( data ) {
  405.                 case "popupNotify":
  406.                     this.popupNotify = this.prefs.getBoolPref("popupNotify");
  407.                     break;
  408.  
  409.                 case "audioNotify":
  410.                     this.audioNotify = this.prefs.getBoolPref("audioNotify");
  411.                     break;
  412.  
  413.                 case "dialogNotify":
  414.                     this.dialogNotify = this.prefs.getBoolPref("dialogNotify");
  415.                     break;
  416.  
  417.                 case "repeatAudio":
  418.                     this.repeatAudio = this.prefs.getIntPref("repeatAudio");
  419.                     break;
  420.  
  421.                 case "repeatMax":
  422.                     this.repeatMax = this.prefs.getIntPref("repeatMax");
  423.                     break;
  424.  
  425.                 case "audioFile":
  426.                     this.audioFile = this.prefs.getIntPref("audioFile");
  427.                     break;
  428.  
  429.                 case "userWavPath":
  430.                     this.customAudioFile = this.prefs.getComplexValue("userWavPath",
  431.                             Components.interfaces.nsILocalFile);
  432.  
  433.                     try {
  434.                         if ( this.customAudioFile.isFile() ) {
  435.                             this.customAudioPath = this.customAudioFile.path;
  436.  
  437.                             if ( this.audioObjectSupport && this.customAudioPath.substr(this.customAudioPath.length - 4) === ".ogg") {
  438.                                 this.usingAudioObject = true;
  439.                                 this.audioObject.src = "file:///" + this.customAudioPath;
  440.                                 this.audioObject.load();
  441.                             }
  442.                             else {
  443.                                 this.usingAudioObject = false;
  444.                             }
  445.                         }
  446.                     }
  447.                     catch(e) {
  448.                         alert(strbundle.getString("alert.error.invalid.audio.file") +
  449.                                 "\n" + e.name);
  450.                     }
  451.  
  452.                     break;
  453.  
  454.                 case "displayIn":
  455.                     this.displayIn = this.prefs.getIntPref("displayIn");
  456.  
  457.                     if ( this.displayIn === 0 ) {
  458.                         // Statusbar.
  459.                         this.setStatusbar();
  460.                     }
  461.                     else if ( this.displayIn === 1 ) {
  462.                         // Toolbar.
  463.                         this.setToolbar();
  464.                     }
  465.                     else {
  466.                         // Both.
  467.                         if ( document.getElementById("simtim-statpanelTimer").hidden ) {
  468.                             // Switching from toolbar display.
  469.                             this.setStatusbar();
  470.  
  471.                             if ( this.buttonOnToolbar ) {
  472.                                 document.getElementById("simtim-toolbarItem").hidden = false;
  473.                             }
  474.                         }
  475.                         else {
  476.                             // Switching from statusbar display.
  477.                             this.setToolbar();
  478.                             document.getElementById("simtim-statpanelTimer").hidden = false;
  479.                         }
  480.                     }
  481.  
  482.                     if ( this.clockInProgress || this.countdownInProgress || this.countupInProgress ) {
  483.                         this.changeBarMode("wake");
  484.                     }
  485.                     else {
  486.                         this.changeBarMode("sleep");
  487.                     }
  488.  
  489.                     break;
  490.  
  491.                 case "onStartup":
  492.                     this.onStartup = this.prefs.getIntPref("onStartup");
  493.                     break;
  494.  
  495.                 case "eventLogging":
  496.                     this.eventLogging = this.prefs.getBoolPref("eventLogging");
  497.                     break;
  498.  
  499.                 case "eventLogPath":
  500.                     this.eventLogPath = this.prefs.getComplexValue("eventLogPath",
  501.                             Components.interfaces.nsILocalFile).path;
  502.                     break;
  503.  
  504.                 case "deleteLogEntries":
  505.                     this.deleteLogEntries = this.prefs.getIntPref("deleteLogEntries");
  506.                     break;
  507.  
  508.                 case "leftClick":
  509.                     this.leftClick = this.prefs.getIntPref("leftClick");
  510.                     break;
  511.  
  512.                 case "disablePauseResume":
  513.                     this.disablePauseResume = this.prefs.getBoolPref("disablePauseResume");
  514.                     break;
  515.  
  516.                 case "tooltip":
  517.                     this.tooltip = this.prefs.getIntPref("tooltip");
  518.                     break;
  519.  
  520.                 case "showSecs":
  521.                     this.showSecs = this.prefs.getBoolPref("showSecs");
  522.                     break;
  523.  
  524.                 case "clockType":
  525.                     this.clockType = this.prefs.getIntPref("clockType");
  526.                     break;
  527.  
  528.                 case "clock24Hr":
  529.                     this.clock24Hr = this.prefs.getBoolPref("clock24Hr");
  530.                     break;
  531.  
  532.                 case "dateFormat":
  533.                     this.dateFormat = this.prefs.getIntPref("dateFormat");
  534.                     break;
  535.  
  536.                 case "countdownHours":
  537.                     this.countdownHours = this.prefs.getIntPref("countdownHours");
  538.                     break;
  539.  
  540.                 case "countdownMinutes":
  541.                     this.countdownMinutes = this.prefs.getIntPref("countdownMinutes");
  542.                     break;
  543.  
  544.                 case "countdownSeconds":
  545.                     this.countdownSeconds = this.prefs.getIntPref("countdownSeconds");
  546.                     break;
  547.  
  548.                 case "notifyClosed":
  549.                     // User clicked OK.
  550.                     if ( this.getNotifyTable() ) {
  551.                         this.clearNotifyInterval();
  552.                         this.setNotifyInterval();
  553.                     }
  554.  
  555.                     break;
  556.  
  557.                 case "countdowns":
  558.                     this.countdowns = this.prefs.getComplexValue("countdowns",
  559.                            Components.interfaces.nsISupportsString).data;
  560.                     break;
  561.  
  562.                 case "favCountdowns":
  563.                     this.favCountdowns = this.prefs.getComplexValue("favCountdowns",
  564.                            Components.interfaces.nsISupportsString).data;
  565.                     break;
  566.  
  567.                 case "notifyRecur":
  568.                     this.notifyRecur = this.prefs.getComplexValue("notifyRecur",
  569.                             Components.interfaces.nsISupportsString).data;
  570.                     break;
  571.  
  572.                 case "notifyTime":
  573.                     // Kept for backward compatibility v0.5 and earlier.
  574.                     break;
  575.  
  576.                 case "insertBefore":
  577.                     break;
  578.  
  579.                 default:
  580.                     alert(strbundle.getString("alert.error.unknown.pref"));
  581.             }
  582.         }
  583.     },
  584.  
  585.     // Called when a window is loaded.
  586.  
  587.     windowLoaded: function(evt) {
  588.         var doc = evt.originalTarget;
  589.         var win = doc.defaultView;
  590.  
  591.         win.removeEventListener("load", function(event) { SimpleTimer.windowLoaded(event); }, false);
  592.  
  593.         if ( doc.documentElement.getAttribute("id") === "CustomizeToolbarWindow" ) {
  594.             // Observe when Customize Toolbar window is closed to see if user
  595.             // dragged button to toolbar.
  596.             var observerService = Components.classes["@mozilla.org/observer-service;1"].
  597.                     getService(Components.interfaces.nsIObserverService);
  598.             observerService.addObserver(this, "domwindowclosed", false);
  599.         }
  600.     },
  601.  
  602.     // Using statusbar.
  603.  
  604.     setStatusbar: function() {
  605.         this.barContainerElement = "simtim-statpanelTimer";
  606.         this.barImageElement = "simtim-statpanelImage";
  607.         this.barLabelElement = "simtim-statpanelLabel";
  608.  
  609.         // Make sure the button is not in the palette.
  610.         if ( this.buttonOnToolbar ) {
  611.             var status = document.getElementById("simtim-toolbarButton").
  612.                     getAttribute("status");
  613.  
  614.             document.getElementById("simtim-statpanelImage").
  615.                     setAttribute("status", status);
  616.             document.getElementById("simtim-statpanelLabel").value =
  617.                     document.getElementById("simtim-toolbarLabel").value;
  618.             document.getElementById("simtim-toolbarItem").hidden = true;
  619.         }
  620.  
  621.         document.getElementById("simtim-statpanelTimer").hidden = false;
  622.     },
  623.  
  624.     // Using toolbar.
  625.  
  626.     setToolbar: function() {
  627.         var strbundle = document.getElementById("simtim-strings");
  628.  
  629.         this.barContainerElement = "simtim-toolbarItem";
  630.         this.barImageElement = "simtim-toolbarButton";
  631.         this.barLabelElement = "simtim-toolbarLabel";
  632.  
  633.         if ( this.buttonOnToolbar ) {
  634.             var status = document.getElementById("simtim-statpanelImage").
  635.                     getAttribute("status");
  636.  
  637.             document.getElementById("simtim-toolbarButton").
  638.                     setAttribute("status", status);
  639.             document.getElementById("simtim-toolbarLabel").value =
  640.                     document.getElementById("simtim-statpanelLabel").value;
  641.             document.getElementById("simtim-toolbarItem").hidden = false;
  642.  
  643.         }
  644.         else {
  645.             // Warn user to move button from palette to toolbar.
  646.             alert(strbundle.getString("alert.warning.toolbar.button"));
  647.         }
  648.  
  649.         document.getElementById("simtim-statpanelTimer").hidden = true;
  650.     },
  651.  
  652.     // Displays a time value on a bar.
  653.  
  654.     displayTime: function(hours, mins, secs) {
  655.         var displayTime = ( hours > 0 ) ?
  656.                            hours + ":" + this.padTime(mins) + ":" + this.padTime(secs) :
  657.                            this.padTime(mins) + ":" + this.padTime(secs);
  658.  
  659.         if ( this.displayIn === 2 ) {
  660.             // Both.
  661.             document.getElementById("simtim-statpanelLabel").value = displayTime;
  662.  
  663.             if ( this.buttonOnToolbar ) {
  664.                 document.getElementById("simtim-toolbarLabel").value = displayTime;
  665.             }
  666.         }
  667.         else {
  668.             var barLabelElement = document.getElementById(this.barLabelElement);
  669.  
  670.             if ( barLabelElement ) {
  671.                 barLabelElement.value = displayTime;
  672.             }
  673.         }
  674.     },
  675.  
  676.     // For tooltips that include countdown timers, refresh tooltip every second.
  677.  
  678.     setTooltip: function() {
  679.         this.fillTooltip();
  680.  
  681.         if ( (( this.tooltip === 4 && this.countdownTable.length > 0 ) ||
  682.                 this.tooltip === 2 ) &&
  683.                 this.timerTooltipID === 0 ) {
  684.             this.timerTooltipID = setInterval("SimpleTimer.fillTooltip()", 1000);
  685.         }
  686.     },
  687.  
  688.     // Called on statpanel or toolbar button mouseout.
  689.  
  690.     clearTooltip: function() {
  691.         clearInterval(this.timerTooltipID);
  692.         this.timerTooltipID = 0;
  693.     },
  694.  
  695.     // Fill the bar tooltip based on user pref.
  696.  
  697.     fillTooltip: function() {
  698.         var strbundle = document.getElementById("simtim-strings");
  699.  
  700.         switch ( this.tooltip ) {
  701.             case 0:
  702.                 // No tooltip.
  703.                 this.completeTooltip("remove", "");
  704.                 break;
  705.  
  706.             case 1:
  707.                 // Date.
  708.                 document.getElementById("simtim-descTtipCurrDate0").value =
  709.                         new Date().toLocaleDateString();
  710.                 this.completeTooltip("set", "simtim-ttipDate");
  711.                 break;
  712.  
  713.             case 2:
  714.                 // Full tooltip.
  715.                 // Date.
  716.                 document.getElementById("simtim-descTtipCurrDate1").value =
  717.                         new Date().toLocaleDateString();
  718.  
  719.                 // Notifications.
  720.                 var parent = document.getElementById("simtim-gridNotify");
  721.                 var oldRowsNode = parent.lastChild;
  722.                 var newRowsNode = document.createElement("rows");
  723.                 var descrHeader = document.createElement("description");
  724.                 var rowNode, descrNodeTime, descrNodeDescription;
  725.                 var msg;
  726.  
  727.                 if ( this.notifyID ) {
  728.                     // Give rows element an id for styling.
  729.                     newRowsNode.setAttribute("id", "simtim-rowsNotify");
  730.  
  731.                     // Display a header line.
  732.                     descrHeader.setAttribute("value",
  733.                             strbundle.getString("msg.notifications"));
  734.                     newRowsNode.appendChild(descrHeader);
  735.  
  736.                     for ( var i in this.notifyTable ) {
  737.                         // Bypass completed nots.
  738.                         this.notification = this.notifyTable[i];
  739.  
  740.                         if ( this.notification.completed ) {
  741.                             continue;
  742.                         }
  743.  
  744.                         rowNode = document.createElement("row");
  745.                         descrNodeTime = document.createElement("description");
  746.                         descrNodeTime.setAttribute("value", this.notification.timeDisplay);
  747.                         rowNode.appendChild(descrNodeTime);
  748.  
  749.                         msg = " ";
  750.                         msg += this.notification.description ?
  751.                                 '"' + this.notification.description + '"' :
  752.                                 strbundle.getString("msg.no.description");
  753.                         msg += this.notification.url ?
  754.                                 " " + "[URL]" : "";
  755.                         msg += this.notification.day >= 0 ?
  756.                                 " / " + SimpleTimerNotify.getDow(this.notification.day) : "";
  757.                         descrNodeDescription = document.createElement("description");
  758.                         descrNodeDescription.setAttribute("value", msg);
  759.                         rowNode.appendChild(descrNodeDescription);
  760.  
  761.                         newRowsNode.appendChild(rowNode);
  762.                     }
  763.                 }
  764.                 else {
  765.                     // No notifications.
  766.                     rowNode = document.createElement("row");
  767.                     descrHeader.setAttribute("value",
  768.                             strbundle.getString("msg.no.notifications"));
  769.                     rowNode.appendChild(descrHeader);
  770.                     newRowsNode.appendChild(rowNode);
  771.                 }
  772.  
  773.                 parent.replaceChild(newRowsNode, oldRowsNode);
  774.  
  775.                 // Countdown timers.
  776.                 parent = document.getElementById("simtim-gridCountdown");
  777.  
  778.                 this.createTimerTooltip(parent);
  779.  
  780.                 // Count up.
  781.                 msg = this.savedCountupTime ?
  782.                         strbundle.getString("msg.last.countup") +
  783.                         " " + this.savedCountupTime :
  784.                         strbundle.getString("msg.no.countup");
  785.                 document.getElementById("simtim-descTtipLastCountUp").value = msg;
  786.  
  787.                 // Browser time.
  788.                 document.getElementById("simtim-descTtipSessionTime").value =
  789.                         strbundle.getString("msg.browser.time") +
  790.                         " " + this.calcBrowserTime();
  791.  
  792.                 // Set tooltip.
  793.                 this.completeTooltip("set", "simtim-ttipFull");
  794.                 break;
  795.  
  796.             case 3:
  797.                 // Date, but only if clock running.
  798.                 if ( this.clockInProgress ) {
  799.                     document.getElementById("simtim-descTtipCurrDate0").value =
  800.                             new Date().toLocaleDateString();
  801.                     this.completeTooltip("set", "simtim-ttipDate");
  802.                 }
  803.                 else {
  804.                     this.completeTooltip("remove", "");
  805.                 }
  806.  
  807.                 break;
  808.  
  809.             case 4:
  810.                 // Countdown timers.
  811.                 parent = document.getElementById("simtim-gridTtipCountdown");
  812.  
  813.                 this.createTimerTooltip(parent);
  814.  
  815.                 // Set tooltip.
  816.                 this.completeTooltip("set", "simtim-ttipCountdown");
  817.                 break;
  818.  
  819.             default:
  820.                 this.debug(strbundle.getString("alert.error.invalid.tooltip"));
  821.         }
  822.     },
  823.  
  824.     // Set or remove tooltip.
  825.  
  826.     completeTooltip: function(action, value) {
  827.         var barContainerElement = document.getElementById(this.barContainerElement);
  828.  
  829.         if ( this.displayIn === 2 ) {
  830.             // Both.
  831.             if ( action === "set") {
  832.                 document.getElementById("simtim-statpanelTimer").setAttribute("tooltip", value);
  833.  
  834.                 if ( this.buttonOnToolbar ) {
  835.                     document.getElementById("simtim-toolbarItem").setAttribute("tooltip", value);
  836.                 }
  837.             }
  838.             else if ( document.getElementById("simtim-statpanelTimer").hasAttribute("tooltip") ) {
  839.                 document.getElementById("simtim-statpanelTimer").removeAttribute("tooltip");
  840.  
  841.                 if ( this.buttonOnToolbar ) {
  842.                     document.getElementById("simtim-toolbarItem").removeAttribute("tooltip");
  843.                 }
  844.             }
  845.         }
  846.         else {
  847.             if ( action === "set") {
  848.                 barContainerElement && barContainerElement.setAttribute("tooltip", value);
  849.             }
  850.             else if ( barContainerElement &&
  851.                       barContainerElement.hasAttribute("tooltip") ) {
  852.                 barContainerElement.removeAttribute("tooltip");
  853.             }
  854.         }
  855.     },
  856.  
  857.     // Countdown timer portion of bar tooltip.
  858.  
  859.     createTimerTooltip: function(parent) {
  860.         // Countdown timers. Create description elements, add them
  861.         // as children to a row element (in a rows elememt in a grid),
  862.         // then replace old rows with new one.
  863.         var strbundle = document.getElementById("simtim-strings");
  864.         var oldRowsNode = parent.lastChild;
  865.         var newRowsNode = document.createElement("rows");
  866.         var descrHeader = document.createElement("description");
  867.         var rowNode, descrNodeTime, descrNodeDescription;
  868.         var msg, displayTime, countdownTimeMilli, countdownTime,
  869.             displayHours, displayMinutes, displaySeconds;
  870.  
  871.         if ( this.countdownTable.length === 0 ) {
  872.             rowNode = document.createElement("row");
  873.             descrHeader.setAttribute("value",
  874.                     strbundle.getString("msg.no.timers"));
  875.             rowNode.appendChild(descrHeader);
  876.             newRowsNode.appendChild(rowNode);
  877.         }
  878.         else {
  879.             if ( this.tooltip === 2 ) {
  880.                 // Give rows element an id for styling.
  881.                 newRowsNode.setAttribute("id", "simtim-rowsTimers");
  882.             }
  883.  
  884.             // Display a header line.
  885.             descrHeader.setAttribute("value",
  886.                     strbundle.getString("msg.countdown.timers"));
  887.             newRowsNode.appendChild(descrHeader);
  888.  
  889.             var currDate = new Date();
  890.             var currTimeMilli = currDate.getTime() - currDate.getMilliseconds();
  891.  
  892.             for ( var j in this.countdownTable ) {
  893.                 rowNode = document.createElement("row");
  894.                 descrNodeTime = document.createElement("description");
  895.  
  896.                 if ( j === "0" && this.paused ) {
  897.                     if ( this.displayIn === 2 ) {
  898.                         displayTime = document.getElementById("simtim-statpanelLabel").value;
  899.                     }
  900.                     else {
  901.                         var barLabelElement = document.getElementById(this.barLabelElement);
  902.  
  903.                         if ( barLabelElement ) {
  904.                             displayTime = barLabelElement.value;
  905.                         }
  906.                     }
  907.  
  908.                     descrNodeTime.setAttribute("value", displayTime);
  909.                 }
  910.                 else {
  911.                     countdownTimeMilli = this.countdownTable[j].timeFinishedMilli - currTimeMilli;
  912.                     countdownTime = new Date(countdownTimeMilli);
  913.                     displayHours = countdownTime.getUTCHours();
  914.                     displayMinutes = this.padTime(countdownTime.getUTCMinutes());
  915.                     displaySeconds = this.padTime(countdownTime.getUTCSeconds());
  916.  
  917.                     if ( countdownTimeMilli > 0 ) {
  918.                         displayTime = ( displayHours > 0 ) ?
  919.                                 displayHours + ":" + displayMinutes + ":" + displaySeconds :
  920.                                 displayMinutes + ":" + displaySeconds;
  921.                     descrNodeTime.setAttribute("value", displayTime);
  922.                     }
  923.                     else {
  924.                         descrNodeTime.setAttribute("value", "00:00");
  925.                     }
  926.                 }
  927.  
  928.                 rowNode.appendChild(descrNodeTime);
  929.  
  930.                 descrNodeDescription = document.createElement("description");
  931.                 msg = this.countdownTable[j].description ?
  932.                         '"' + this.countdownTable[j].description + '"' :
  933.                         strbundle.getString("msg.no.description");
  934.  
  935.                 msg += this.countdownTable[j].recurring ?
  936.                         " / " + strbundle.getString("msg.recurring") : "";
  937.                 descrNodeDescription.setAttribute("value", msg);
  938.                 rowNode.appendChild(descrNodeDescription);
  939.  
  940.                 newRowsNode.appendChild(rowNode);
  941.             }
  942.         }
  943.  
  944.         parent.replaceChild(newRowsNode, oldRowsNode);
  945.     },
  946.  
  947.     // Check if there is a timer already in progress.
  948.     // If last action was count up, save time for tooltip.
  949.  
  950.     cancelTimerInProgress: function() {
  951.         var strbundle = document.getElementById("simtim-strings");
  952.  
  953.         if ( this.timerID ) {
  954.              window.clearInterval(window.SimpleTimer.timerID);
  955.             this.timerID = 0;
  956.             this.startTime = null;
  957.  
  958.             if ( this.displayIn === 2 ) {
  959.                 // Both.
  960.                 document.getElementById("simtim-statpanelLabel").value = "";
  961.  
  962.                 if ( this.buttonOnToolbar ) {
  963.                     document.getElementById("simtim-toolbarLabel").value = "";
  964.                 }
  965.             }
  966.             else {
  967.                 var barLabelElement = document.getElementById(this.barLabelElement);
  968.  
  969.                 if ( barLabelElement ) {
  970.                     barLabelElement.value = "";
  971.                 }
  972.             }
  973.         }
  974.  
  975.         if ( this.countupInProgress ) {
  976.             this.countupInProgress = false;
  977.             this.savedCountupTime = this.padTime(this.elapsedHours) + ":" +
  978.                     this.padTime(this.elapsedMins) + ":" +
  979.                     this.padTime(this.elapsedSecs);
  980.  
  981.             if ( this.eventLogging ) {
  982.                 // Pass event type, event time, recurring, status, description(N/A), URL(N/A).
  983.                 SimpleTimerEventLog.logEvent(strbundle.getString("msg.countup"),
  984.                                               this.savedCountupTime,
  985.                                               strbundle.getString("msg.not.applicable"),
  986.                                               strbundle.getString("msg.completed"),
  987.                                               strbundle.getString("msg.not.applicable"),
  988.                                               strbundle.getString("msg.not.applicable"));
  989.             }
  990.         }
  991.  
  992.         this.countdownInProgress = false;
  993.         this.clockInProgress = false;
  994.         this.paused = false;
  995.     },
  996.  
  997.     // Change the bar display between icon and label.
  998.  
  999.     changeBarMode: function(mode) {
  1000.         if ( mode === "wake" ) {
  1001.             // Wake up panel if sleeping.
  1002.             if ( this.displayIn === 2 ) {
  1003.                 // Both.
  1004.                 if ( document.getElementById("simtim-statpanelLabel").collapsed ) {
  1005.                     document.getElementById("simtim-statpanelLabel").value = "";
  1006.                     document.getElementById("simtim-statpanelLabel").collapsed = false;
  1007.                     document.getElementById("simtim-statpanelImage").collapsed = true;
  1008.                 }
  1009.  
  1010.                 if ( this.buttonOnToolbar &&
  1011.                      document.getElementById("simtim-toolbarLabel").collapsed ) {
  1012.                     document.getElementById("simtim-toolbarLabel").value = "";
  1013.                     document.getElementById("simtim-toolbarLabel").collapsed = false;
  1014.                     document.getElementById("simtim-toolbarButton").collapsed = true;
  1015.                 }
  1016.             }
  1017.             else if ( document.getElementById(this.barContainerElement) &&
  1018.                       document.getElementById(this.barLabelElement).collapsed ) {
  1019.                 document.getElementById(this.barLabelElement).value = "";
  1020.                 document.getElementById(this.barLabelElement).collapsed = false;
  1021.                 document.getElementById(this.barImageElement).collapsed = true;
  1022.             }
  1023.         }
  1024.         else {
  1025.             // Sleep panel if awake.
  1026.             if ( this.displayIn === 2 ) {
  1027.                 // Both.
  1028.                 if ( document.getElementById("simtim-statpanelImage").collapsed ) {
  1029.                     document.getElementById("simtim-statpanelImage").collapsed = false;
  1030.                     document.getElementById("simtim-statpanelLabel").collapsed = true;
  1031.                 }
  1032.  
  1033.                 if ( this.buttonOnToolbar &&
  1034.                      document.getElementById("simtim-toolbarButton").collapsed ) {
  1035.                     document.getElementById("simtim-toolbarButton").collapsed = false;
  1036.                     document.getElementById("simtim-toolbarLabel").collapsed = true;
  1037.                 }
  1038.             }
  1039.             else if ( document.getElementById(this.barContainerElement) &&
  1040.                       document.getElementById(this.barImageElement).collapsed ) {
  1041.                 document.getElementById(this.barImageElement).collapsed = false;
  1042.                 document.getElementById(this.barLabelElement).collapsed = true;
  1043.             }
  1044.         }
  1045.     },
  1046.  
  1047.     // Calculate elapsed time this browser session, displayed in tooltip.
  1048.  
  1049.     calcBrowserTime: function() {
  1050.         // Round elapsed milli to nearest second
  1051.         var browseMilli = new Date().getTime() - this.startBrowserTime.getTime();
  1052.         browseMilli = Math.round(browseMilli / 1000) * 1000;
  1053.         var browseSecs = ( browseMilli / 1000 ) % 60;
  1054.         var browseMins = (Math.floor(browseMilli / ( 1000 * 60 ))) % 60;
  1055.         var browseHours = Math.floor(browseMilli / ( 1000 * 60 * 60 ));
  1056.  
  1057.         return ( this.padTime(browseHours) + ":" +
  1058.                  this.padTime(browseMins) + ":" +
  1059.                  this.padTime(browseSecs) );
  1060.     },
  1061.  
  1062.     // Calculate elapsed time. Used by both countup and countdown.
  1063.  
  1064.     calcElapsedTime: function() {
  1065.         // Round elapsed milli to nearest second.
  1066.         this.elapsedMilli = new Date().getTime() - this.startTime.getTime();
  1067.         this.elapsedMilli = Math.round(this.elapsedMilli / 1000) * 1000;
  1068.         this.elapsedSecs = ( this.elapsedMilli / 1000 ) % 60;
  1069.         this.elapsedMins = (Math.floor(this.elapsedMilli / ( 1000 * 60 ))) % 60;
  1070.         this.elapsedHours = Math.floor(this.elapsedMilli / ( 1000 * 60 * 60 ));
  1071.     },
  1072.  
  1073.     // Calculate remaining time for countdown.
  1074.  
  1075.     calcRemainingTime: function() {
  1076.         this.remainingMilli  = this.countdownMilli - this.elapsedMilli;
  1077.         this.remainingSecs  = ( this.remainingMilli / 1000 ) % 60;
  1078.         this.remainingMins =  (Math.floor(this.remainingMilli / ( 1000 * 60 ))) % 60;
  1079.         this.remainingHours = Math.floor(this.remainingMilli / ( 1000 * 60 * 60 ));
  1080.     },
  1081.  
  1082.     // Pad a display time with zero if necessary.
  1083.  
  1084.     padTime: function(time) {
  1085.         return ( ( time < 10 ) ? "0" + time : time );
  1086.     },
  1087.  
  1088.     // Convert a clock (hh:mm:ss) to milliseconds.
  1089.  
  1090.     convertClockToMilli: function(clock) {
  1091.         return ( ( parseInt(clock, 10) * 60 * 60 * 1000 ) +
  1092.                  ( parseInt(clock.substr(clock.indexOf(":") + 1), 10) * 60 * 1000 ) +
  1093.                  ( parseInt(clock.substr(clock.lastIndexOf(":") + 1), 10) * 1000 ) );
  1094.     },
  1095.  
  1096.     // Left-click on icon is user-defined. Left-click during count up
  1097.     // or count down is pause/resume (can be disabled in Options). Middle-click is stop/reset.
  1098.  
  1099.     onPanelClick: function(event) {
  1100.         if ( event.button === 0 ) {
  1101.             // Process left mouse clicks.
  1102.             if ( this.countdownInProgress || this.countupInProgress ) {
  1103.                 if ( !this.disablePauseResume ) {
  1104.                     this.pauseResume();
  1105.                 }
  1106.             }
  1107.             else if ( this.clockInProgress ) {
  1108.                 // something someday.
  1109.             }
  1110.             else {
  1111.                 // Icon must be displayed.
  1112.                 switch ( this.leftClick ) {
  1113.                     case 0:
  1114.                         break;
  1115.  
  1116.                     case 1:
  1117.                         this.clock();
  1118.                         break;
  1119.  
  1120.                     case 2:
  1121.                         this.countUp();
  1122.                         break;
  1123.  
  1124.                     case 3:
  1125.                         this.countDown();
  1126.                         break;
  1127.  
  1128.                     case 4:
  1129.                         this.notifyMe();
  1130.                         break;
  1131.  
  1132.                     default:
  1133.                         var strbundle = document.getElementById("simtim-strings");
  1134.                         alert(strbundle.getString("alert.error.invalid.leftClick"));
  1135.                 }
  1136.             }
  1137.         }
  1138.         else if ( event.button === 1 &&
  1139.                 ( this.timerID || this.paused )) {
  1140.             // Middle-click. Paused countup has no timer id.
  1141.             this.stop();
  1142.         }
  1143.     },
  1144.  
  1145.     // Called when user selects Clock menuitem, or left-click
  1146.     // bar icon if defined.
  1147.  
  1148.     clock: function() {
  1149.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
  1150.         var wmed = wm.QueryInterface(Components.interfaces.nsIWindowMediator);
  1151.         var enumerator = wmed.getEnumerator("navigator:browser");
  1152.  
  1153.         while ( enumerator.hasMoreElements() ) {
  1154.             var win = enumerator.getNext();
  1155.  
  1156.             win.SimpleTimer.cancelTimerInProgress();
  1157.  
  1158.             if ( win.SimpleTimer.countdowns ) {
  1159.                 win.SimpleTimer.countdownTable = [];
  1160.                 win.SimpleTimer.setCountdownTable();
  1161.                 win.SimpleTimerCountdown.updateCountdownPref();
  1162.             }
  1163.  
  1164.             win.SimpleTimer.changeBarMode("wake");
  1165.             win.SimpleTimer.startTime = new Date();
  1166.             win.SimpleTimer.clockInProgress = true;
  1167.  
  1168.             win.SimpleTimer.timerID = win.setInterval(win.SimpleTimer.displayClock, 1000, "clock");
  1169.         }
  1170.     },
  1171.  
  1172.     // Display a clock on statusbar or toolbar using current time (mode = "clock").
  1173.     // Return time to Timer Completed dialog (mode = "dialog").
  1174.  
  1175.     displayClock: function(mode) {
  1176.         var displayTime = "";
  1177.         var date = new Date();
  1178.  
  1179.         if ( SimpleTimer.clockType === 1 ) {
  1180.             // Display Epoch time, seconds since Jan 1, 1970 GMT 0:00.
  1181.             displayTime = ( date.getTime() - date.getMilliseconds() ) / 1000;
  1182.         }
  1183.         else if ( SimpleTimer.clock24Hr ) {
  1184.             // User preference is to use 24 hr clock.
  1185.             if ( SimpleTimer.showSecs ) {
  1186.                 // User preference is to show seconds.
  1187.                 displayTime = SimpleTimer.padTime(date.getHours()) +
  1188.                     ":" + SimpleTimer.padTime(date.getMinutes()) +
  1189.                     ":" + SimpleTimer.padTime(date.getSeconds());
  1190.             }
  1191.             else {
  1192.                 displayTime = SimpleTimer.padTime(date.getHours()) +
  1193.                     ":" + SimpleTimer.padTime(date.getMinutes());
  1194.             }
  1195.         }
  1196.         else {
  1197.             if ( SimpleTimer.showSecs ) {
  1198.                 displayTime = date.toLocaleTimeString();
  1199.             }
  1200.             else {
  1201.                 var time = date.toLocaleTimeString().split(" ");
  1202.  
  1203.                 if ( time.length === 1 || time[0].indexOf(":") >= 0 ) {
  1204.                     // Local time is 24 hr clock, or 12 hr clock where
  1205.                     // time value precedes AM/PM chars (typical).
  1206.                     // Discard the seconds.
  1207.                     displayTime = time[0].substr(0, time[0].lastIndexOf(":"));
  1208.  
  1209.                     // If local time is 12 hr, time[1] will be AM/PM chars.
  1210.                     displayTime = ( time.length > 1 ) ?
  1211.                                 displayTime + " " + time[1] : displayTime;
  1212.                 }
  1213.                 else {
  1214.                     // AM/PM chars. precede time value eg. Korean
  1215.                     displayTime = time[0] + " " +
  1216.                             time[1].substr(0, time[1].lastIndexOf(":"));
  1217.                 }
  1218.             }
  1219.         }
  1220.  
  1221.         if ( mode === "clock") {
  1222.             // Append a date as indicated by user in Options.
  1223.             displayTime += SimpleTimer.getFormattedDate(date);
  1224.  
  1225.             if ( SimpleTimer.displayIn === 2 ) {
  1226.                 // Both.
  1227.                 document.getElementById("simtim-statpanelLabel").value = displayTime;
  1228.  
  1229.                 if ( SimpleTimer.buttonOnToolbar ) {
  1230.                     document.getElementById("simtim-toolbarLabel").value = displayTime;
  1231.                 }
  1232.             }
  1233.             else {
  1234.                 var barLabelElement = document.getElementById(SimpleTimer.barLabelElement);
  1235.  
  1236.                 if ( barLabelElement ) {
  1237.                     barLabelElement.value = displayTime;
  1238.                 }
  1239.             }
  1240.         }
  1241.  
  1242.         // Return value used in Timer Completed load routine.
  1243.         // Ignored in clock mode.
  1244.         return displayTime;
  1245.     },
  1246.  
  1247.     // Return a formatted date as indicated by user in Options.
  1248.  
  1249.     getFormattedDate: function(date) {
  1250.         switch ( this.dateFormat ) {
  1251.             case 0:
  1252.                 // No date.
  1253.                 return "";
  1254.  
  1255.             case 1:
  1256.                 // eg. Fri.
  1257.                 return date.toLocaleFormat(" %a");
  1258.  
  1259.             case 2:
  1260.                 // eg. Fri May 29.
  1261.                 return date.toLocaleFormat(" %a  %b %d");
  1262.  
  1263.             case 3:
  1264.                 // eg. Fri May 29, 2009.
  1265.                 return date.toLocaleFormat(" %a  %b %d, %Y");
  1266.  
  1267.             case 4:
  1268.                 // eg. Fri 05/29/09.
  1269.                 return date.toLocaleFormat(" %a %m/%d/%y");
  1270.  
  1271.             case 5:
  1272.                 // eg. Fri 05/29/2009.
  1273.                 return date.toLocaleFormat(" %a %m/%d/%Y");
  1274.  
  1275.             case 6:
  1276.                 // eg. Fri 29/05/09.
  1277.                 return date.toLocaleFormat(" %a %d/%m/%y");
  1278.  
  1279.             case 7:
  1280.                 // eg. Fri 29/05/2009.
  1281.                 return date.toLocaleFormat(" %a %d/%m/%Y");
  1282.  
  1283.              case 8:
  1284.                 // eg. Fri 29.
  1285.                 return date.toLocaleFormat(" %a %d");
  1286.  
  1287.             case 9:
  1288.                 // eg. Fri 29th.
  1289.                 var numDate = date.getDate();
  1290.                 return date.toLocaleFormat(" %a") + " " +
  1291.                         numDate + this.getOrdinalChars(numDate);
  1292.  
  1293.             default:
  1294.                 var strbundle = document.getElementById("simtim-strings");
  1295.                 alert(strbundle.getString("alert.error.invalid.dateFormat"));
  1296.                 return "";
  1297.         }
  1298.     },
  1299.  
  1300.     // Return chars representing ordinal.
  1301.  
  1302.     getOrdinalChars: function(num) {
  1303.         var strbundle = document.getElementById("simtim-strings");
  1304.  
  1305.         return ( num % 10 === 1 && num % 100 !== 11 ) ? strbundle.getString("display.ordinal.st") :
  1306.                ( num % 10 === 2 && num % 100 !== 12 ) ? strbundle.getString("display.ordinal.nd") :
  1307.                ( num % 10 === 3 && num % 100 !== 13 ) ? strbundle.getString("display.ordinal.rd") :
  1308.                                                         strbundle.getString("display.ordinal.th");
  1309.     },
  1310.  
  1311.     // Called when user selects Notify Me At menuitem.
  1312.  
  1313.     notifyMe: function() {
  1314.         this.setNotifyTable();
  1315.  
  1316.         // Display Notify Me At dialog, non-modal.
  1317.         window.openDialog(
  1318.                 "chrome://simpletimer/content/simpletimer-notify.xul",
  1319.                 "",
  1320.                 "centerscreen, chrome, resizable, dependent");
  1321.     },
  1322.  
  1323.     // Called at startup and after Notify Me At dialog closed (via OK).
  1324.  
  1325.     getNotifyTable: function() {
  1326.         var strbundle = document.getElementById("simtim-strings");
  1327.         var Application = Components.classes["@mozilla.org/fuel/application;1"].
  1328.                 getService(Components.interfaces.fuelIApplication);
  1329.  
  1330.         this.notifyTable = Application.storage.get("notifyTable", "ERR");
  1331.  
  1332.         if ( this.notifyTable === "ERR" ) {
  1333.             alert(strbundle.getString("alert.error.loading.notifications"));
  1334.             return false;
  1335.         }
  1336.         else {
  1337.             return true;
  1338.         }
  1339.     },
  1340.  
  1341.     // Called before Notify Me At dialog opened.
  1342.  
  1343.     setNotifyTable: function() {
  1344.         var Application = Components.classes["@mozilla.org/fuel/application;1"].
  1345.                 getService(Components.interfaces.fuelIApplication);
  1346.  
  1347.         Application.storage.set("notifyTable", this.notifyTable);
  1348.  
  1349.     },
  1350.  
  1351.     // Set icon image based on pending nots.
  1352.  
  1353.     setIconImage: function(elem) {
  1354.         // Check if any pending nots., and set icon image
  1355.         var status = "normal";
  1356.         var len = this.notifyTable.length;
  1357.  
  1358.         for ( var i = 0; i < len; i++ ) {
  1359.             if ( this.notifyTable[i].completed === false ) {
  1360.                 status = "notify";
  1361.                 break;
  1362.             }
  1363.         }
  1364.  
  1365.         if ( elem === "both" ) {
  1366.             document.getElementById("simtim-statpanelImage").
  1367.                     setAttribute("status", status);
  1368.  
  1369.             if ( this.buttonOnToolbar ) {
  1370.                 document.getElementById("simtim-toolbarButton").
  1371.                         setAttribute("status", status);
  1372.             }
  1373.         }
  1374.         else {
  1375.             document.getElementById(this.barImageElement) &&
  1376.             document.getElementById(this.barImageElement).
  1377.                     setAttribute("status", status);
  1378.         }
  1379.     },
  1380.  
  1381.     // Clear notify interval.
  1382.  
  1383.     clearNotifyInterval: function() {
  1384.         this.setIconImage();
  1385.         clearInterval(this.notifyID);
  1386.         this.notifyID = 0;
  1387.     },
  1388.  
  1389.     // Set notify interval based on soonest pending notify.
  1390.  
  1391.     setNotifyInterval: function() {
  1392.         if ( !this.notifyTable || this.notifyTable.length === 0 ) {
  1393.             return;
  1394.         }
  1395.  
  1396.         var intervalTime = 0;
  1397.  
  1398.         // For the not. selected to time, this is the index into the not. table.
  1399.         this.currNotifyIndex = 0;
  1400.  
  1401.         // For the not. selected to time, this is the time until completion.
  1402.         this.currNotifyInterval = 0;
  1403.  
  1404.         var currDate = new Date();
  1405.         var currTimeMilli = currDate.getTime() - ( currDate.getSeconds() * 1000 );
  1406.  
  1407.         // Find the not. with the smallest interval time.
  1408.         for ( var i in this.notifyTable ) {
  1409.             // Bypass completed nots.
  1410.             if ( this.notifyTable[i].completed === true ) {
  1411.                 continue;
  1412.             }
  1413.  
  1414.             intervalTime = this.notifyTable[i].timeMilli - currTimeMilli;
  1415.  
  1416.             if ( this.currNotifyInterval === 0 ||
  1417.                  intervalTime < this.currNotifyInterval ) {
  1418.                 this.currNotifyInterval = intervalTime;
  1419.                 this.currNotifyIndex = i;
  1420.             }
  1421.         }
  1422.  
  1423.         // No point checking for completion every second if we are more than
  1424.         // a minute from completion.
  1425.         if ( this.currNotifyInterval ) {
  1426.             this.notifyID = ( this.currNotifyInterval > ( 60 * 1000 ) ) ?
  1427.                     setTimeout("SimpleTimer.switchNotifyInterval()",
  1428.                             (this.currNotifyInterval - ( 60 * 1000 ))) :
  1429.                     setInterval("SimpleTimer.monitorLastMinuteNotify()", 1000);
  1430.         }
  1431.     },
  1432.  
  1433.     // Change intervals when its one minute prior to notify. Called once.
  1434.  
  1435.     switchNotifyInterval: function() {
  1436.         clearTimeout(this.notifyID);
  1437.         this.notifyID = setInterval("SimpleTimer.monitorLastMinuteNotify()", 1000);
  1438.     },
  1439.  
  1440.     // Is it time to notify the user?
  1441.  
  1442.     monitorLastMinuteNotify: function() {
  1443.         var currDate = new Date();
  1444.  
  1445.         if ( currDate.getTime() > this.notifyTable[this.currNotifyIndex].timeMilli ) {
  1446.             // We're done.
  1447.             var timeoutIncrement = 0;
  1448.             this.completedIndices = [];
  1449.  
  1450.             this.clearNotifyInterval();
  1451.  
  1452.             // Check if multiple nots. expired at same time.
  1453.             // Add a 3 second delay between the alerts.
  1454.             for ( var i in this.notifyTable ) {
  1455.                 if ( this.notifyTable[i].completed === false &&
  1456.                      currDate.getTime() > this.notifyTable[i].timeMilli ) {
  1457.                     this.completedIndices.push(i);
  1458.                     setTimeout("SimpleTimer.processCompletedNotify()", timeoutIncrement);
  1459.                     setTimeout("SimpleTimer.setIconImage()", timeoutIncrement + 1000);
  1460.  
  1461.                     if ( !this.notifyTable[i].recurring || this.notifyToday(i) ) {
  1462.                         timeoutIncrement +=  3000;
  1463.                     }
  1464.                 }
  1465.             }
  1466.  
  1467.             timeoutIncrement += 1000;
  1468.             setTimeout("SimpleTimer.setNotifyInterval()", timeoutIncrement);
  1469.         }
  1470.     },
  1471.  
  1472.     // A completed notification is not deleted from table,
  1473.     // but updated.
  1474.  
  1475.     processCompletedNotify: function() {
  1476.         var strbundle = document.getElementById("simtim-strings");
  1477.  
  1478.         if ( this.completedIndices.length > 0 ) {
  1479.             // Multiple nots. expired at the same time.
  1480.             // Grab (and remove) the first element (not. table index).
  1481.             this.currNotifyIndex = this.completedIndices.shift();
  1482.         }
  1483.  
  1484.         var recurring = this.notifyTable[this.currNotifyIndex].recurring;
  1485.  
  1486.         if ( !recurring || this.notifyToday(this.currNotifyIndex) ) {
  1487.             this.checkUserPrefs("notify");
  1488.  
  1489.             // Link if URL provided but dialog alarm not used.
  1490.             if ( this.notifyTable[this.currNotifyIndex].url  &&
  1491.                  !this.dialogNotify &&
  1492.                  ( this.audioNotify ||
  1493.                    this.popupNotify ) ) {
  1494.                 this.autoOpenLink(this.notifyTable[this.currNotifyIndex].url);
  1495.             }
  1496.  
  1497.             if ( this.eventLogging ) {
  1498.                 var eventTime = this.notifyTable[this.currNotifyIndex].timeDisplay;
  1499.                 var eventRecurring = ( recurring ) ?
  1500.                         SimpleTimerNotify.getDow(this.notifyTable[this.currNotifyIndex].day) :
  1501.                         strbundle.getString("msg.no");
  1502.                 var eventDescription = ( this.notifyTable[this.currNotifyIndex].description ) ?
  1503.                         this.notifyTable[this.currNotifyIndex].description :
  1504.                         strbundle.getString("msg.none");
  1505.                 var eventUrl = ( this.notifyTable[this.currNotifyIndex].url ) ?
  1506.                         this.notifyTable[this.currNotifyIndex].url :
  1507.                         strbundle.getString("msg.none");
  1508.                 // Pass event type, event time, recurring, status, description, URL.
  1509.                 SimpleTimerEventLog.logEvent(strbundle.getString("msg.notification"),
  1510.                                              eventTime,
  1511.                                              eventRecurring,
  1512.                                              strbundle.getString("msg.completed"),
  1513.                                              eventDescription,
  1514.                                              eventUrl);
  1515.             }
  1516.         }
  1517.  
  1518.         if ( recurring ) {
  1519.             // Recurring ones don't get marked completed.
  1520.             // Add 1 day to not. milli time.
  1521.             var notDate = new Date(this.notifyTable[this.currNotifyIndex].timeMilli);
  1522.             notDate.setDate(notDate.getDate() + 1);
  1523.             this.notifyTable[this.currNotifyIndex].timeMilli = notDate.getTime();
  1524.         }
  1525.         else {
  1526.             // Mark this notification as completed.
  1527.             this.notifyTable[this.currNotifyIndex].completed = true;
  1528.         }
  1529.  
  1530.         // Update notifications pref.
  1531.         this.setNotifyTable();
  1532.         SimpleTimerNotify.updateNotifyPref();
  1533.     },
  1534.  
  1535.     // Is today a notification day?
  1536.  
  1537.     notifyToday: function(index) {
  1538.         // Not. day values:
  1539.         // 0-6 for Sun-Sat,
  1540.         // 7 daily, 8 weekdays, 9 weekends.
  1541.         var currDay = new Date().getDay();
  1542.         var notDay = this.notifyTable[index].day;
  1543.  
  1544.         if ( notDay === 7 || notDay === currDay ||
  1545.              ( notDay === 8 && ( currDay > 0 && currDay < 6 ) ) ||
  1546.              ( notDay === 9 && ( currDay === 0 || currDay === 6 ) ) ) {
  1547.             return true;
  1548.         }
  1549.         else {
  1550.             return false;
  1551.         }
  1552.     },
  1553.  
  1554.     // Called when user selects Count Down menuitem.
  1555.  
  1556.     countDown: function() {
  1557.         this.setCountdownTable();
  1558.  
  1559.         // Display time entry dialog, non-modal.
  1560.         window.openDialog(
  1561.                 "chrome://simpletimer/content/simpletimer-countdown.xul",
  1562.                 "",
  1563.                 "centerscreen, chrome, resizable, dependent");
  1564.     },
  1565.  
  1566.     // Called after Countdown time entry dialog closed (via OK).
  1567.  
  1568.     getCountdownTable: function() {
  1569.         var strbundle = document.getElementById("simtim-strings");
  1570.         var Application = Components.classes["@mozilla.org/fuel/application;1"].
  1571.                 getService(Components.interfaces.fuelIApplication);
  1572.  
  1573.         var table = Application.storage.get("countdownTable", "ERR");
  1574.  
  1575.         if ( table === "ERR" ) {
  1576.             alert(strbundle.getString("alert.error.loading.countdowns"));
  1577.             return false;
  1578.         }
  1579.         else {
  1580.             // Independent copy of the array of countdown objects.
  1581.             this.countdownTable = this.deepCopy(table);
  1582.             return true;
  1583.         }
  1584.     },
  1585.  
  1586.     // Called before Countdown time entry dialog opened.
  1587.  
  1588.     setCountdownTable: function() {
  1589.         var Application = Components.classes["@mozilla.org/fuel/application;1"].
  1590.                 getService(Components.interfaces.fuelIApplication);
  1591.  
  1592.         var table = this.deepCopy(this.countdownTable);
  1593.  
  1594.         Application.storage.set("countdownTable", table);
  1595.  
  1596.     },
  1597.  
  1598.     // Start countdown.
  1599.  
  1600.     setCountdownTimer: function() {
  1601.         if ( this.countdownTable.length === 0 ) {
  1602.             SimpleTimer.cancelTimerInProgress();
  1603.             SimpleTimer.changeBarMode("sleep");
  1604.  
  1605.             // Update countdowns pref.
  1606.             this.setCountdownTable();
  1607.             SimpleTimerCountdown.updateCountdownPref();
  1608.  
  1609.             return;
  1610.         }
  1611.  
  1612.         this.startTime = new Date();
  1613.         var currTimeMilli = this.startTime.getTime() - this.startTime.getMilliseconds();
  1614.  
  1615.         for ( var i in this.countdownTable ) {
  1616.             // For new timers set the completion time.
  1617.             // Paused timer will stay on top (timeFinishedMilli = 0) until resumed.
  1618.             if ( this.countdownTable[i].timeFinishedMilli === 0 &&
  1619.                  !this.countdownTable[i].paused ) {
  1620.                 this.countdownTable[i].timeFinishedMilli =
  1621.                         currTimeMilli + this.countdownTable[i].timeMilli;
  1622.             }
  1623.         }
  1624.  
  1625.         // Sort the table. If the first entry is recurring, it may no longer be next to expire.
  1626.         if ( this.countdownTable.length > 1 ) {
  1627.             this.countdownTable.sort(SimpleTimerCountdown.sortCountdownTable);
  1628.         }
  1629.  
  1630.         // Update countdowns pref.
  1631.         this.setCountdownTable();
  1632.         SimpleTimerCountdown.updateCountdownPref();
  1633.  
  1634.         if ( this.countdownTable[0].paused ) {
  1635.             // timeMilli was set to remainingMilli when paused.
  1636.             this.paused = true;
  1637.             this.countdownMilli = this.countdownTable[0].timeMilli;
  1638.         }
  1639.         else {
  1640.             this.paused = false;
  1641.             this.countdownMilli = this.countdownTable[0].timeFinishedMilli - currTimeMilli;
  1642.         }
  1643.  
  1644.         if ( this.countdownMilli < 0 ) {
  1645.             // Sanity.
  1646.             this.countdownMilli = 0;
  1647.         }
  1648.  
  1649.         var time = new Date(this.countdownMilli);
  1650.         this.countdownHours = time.getUTCHours();
  1651.         this.countdownMinutes = time.getUTCMinutes();
  1652.         this.countdownSeconds = time.getUTCSeconds();
  1653.  
  1654.         SimpleTimer.changeBarMode("wake");
  1655.         this.displayTime(this.countdownHours, this.countdownMinutes,
  1656.                          this.countdownSeconds);
  1657.  
  1658.         this.countdownInProgress = true;
  1659.  
  1660.         // Perform decreaseTimer() every second.
  1661.         this.timerID = setInterval("SimpleTimer.decreaseTimer()", 1000);
  1662.     },
  1663.  
  1664.     // Display the updated timer.
  1665.  
  1666.     decreaseTimer: function() {
  1667.         var strbundle = document.getElementById("simtim-strings");
  1668.  
  1669.         if ( !this.paused ) {
  1670.             this.calcElapsedTime();
  1671.         }
  1672.  
  1673.         if ( this.elapsedMilli < this.countdownMilli ) {
  1674.             // Check if others (besides first timer/element) have expired.
  1675.             // Possible if timer paused.
  1676.             if ( this.countdownTable.length > 1 ) {
  1677.                 var eventDescription, eventRecurring;
  1678.                 var currDate = new Date();
  1679.                 var currTimeMilli = currDate.getTime() - currDate.getMilliseconds();
  1680.                 var len = this.countdownTable.length;
  1681.  
  1682.                 for ( var i = 1; i < len; i++ ) {
  1683.                     if ( currTimeMilli >= this.countdownTable[i].timeFinishedMilli ) {
  1684.                         // We're done.
  1685.                         this.checkUserPrefs(i); // Pass index.
  1686.  
  1687.                         if ( this.eventLogging ) {
  1688.                             eventRecurring = ( this.countdownTable[i].recurring ) ?
  1689.                                     strbundle.getString("msg.yes") :
  1690.                                     strbundle.getString("msg.no");
  1691.                             eventDescription = ( this.countdownTable[i].description ) ?
  1692.                                     this.countdownTable[i].description :
  1693.                                     strbundle.getString("msg.none");
  1694.  
  1695.                             // Pass event type, event time, recurring, status, description, URL(N/A).
  1696.                             SimpleTimerEventLog.logEvent(strbundle.getString("msg.countdown"),
  1697.                                                          this.countdownTable[i].timeDisplay,
  1698.                                                          eventRecurring,
  1699.                                                          strbundle.getString("msg.completed"),
  1700.                                                          eventDescription,
  1701.                                                          strbundle.getString("msg.not.applicable"));
  1702.                         }
  1703.  
  1704.                         if ( this.countdownTable[i].recurring ) {
  1705.                             // Cannot increment with timeMilli, since it changes if paused.
  1706.                             this.countdownTable[i].timeFinishedMilli +=
  1707.                                     this.convertClockToMilli(this.countdownTable[i].timeDisplay);
  1708.                         }
  1709.                         else {
  1710.                             // Remove the completed timer.
  1711.                             this.countdownTable.splice(i, 1);
  1712.  
  1713.                             // Update countdowns pref.
  1714.                             this.setCountdownTable();
  1715.                             SimpleTimerCountdown.updateCountdownPref();
  1716.                         }
  1717.                     }
  1718.                 }
  1719.             }
  1720.  
  1721.             if ( !this.paused ) {
  1722.                 // Update the statpanel display.
  1723.                 this.calcRemainingTime();
  1724.                 this.displayTime(this.remainingHours, this.remainingMins,
  1725.                                  this.remainingSecs);
  1726.             }
  1727.         }
  1728.         else {
  1729.             // We're done.
  1730.             this.cancelTimerInProgress();
  1731.             this.displayTime(0, 0, 0);
  1732.             this.checkUserPrefs("0"); // Pass index.
  1733.  
  1734.             if ( this.eventLogging ) {
  1735.                 eventRecurring = ( this.countdownTable[0].recurring ) ?
  1736.                         strbundle.getString("msg.yes") :
  1737.                         strbundle.getString("msg.no");
  1738.                 eventDescription = ( this.countdownTable[0].description ) ?
  1739.                         this.countdownTable[0].description :
  1740.                         strbundle.getString("msg.none");
  1741.  
  1742.                 // Pass event type, event time, recurring, status, description, URL(N/A).
  1743.                 SimpleTimerEventLog.logEvent(strbundle.getString("msg.countdown"),
  1744.                                              this.countdownTable[0].timeDisplay,
  1745.                                              eventRecurring,
  1746.                                              strbundle.getString("msg.completed"),
  1747.                                              eventDescription,
  1748.                                              strbundle.getString("msg.not.applicable"));
  1749.             }
  1750.  
  1751.             if ( this.countdownTable[0].recurring ) {
  1752.                 // Cannot increment with timeMilli, since it changes if paused.
  1753.                 this.countdownTable[0].timeFinishedMilli +=
  1754.                         this.convertClockToMilli(this.countdownTable[0].timeDisplay);
  1755.                 this.setCountdownTimer();
  1756.             }
  1757.             else {
  1758.                 if ( this.countdownTable.length < 2 ) {
  1759.                     this.countdownTable = [];
  1760.  
  1761.                     // Update countdowns pref.
  1762.                     this.setCountdownTable();
  1763.                     SimpleTimerCountdown.updateCountdownPref();
  1764.  
  1765.                     if ( this.onStartup === 1 ) {
  1766.                         // Make sure all windows have completed countdown processing,
  1767.                         // as clock() cancels timer ids for all windows.
  1768.                         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
  1769.                         var wmed = wm.QueryInterface(Components.interfaces.nsIWindowMediator);
  1770.                         var enumerator = wmed.getEnumerator("navigator:browser");
  1771.                         var allWinComplete = true;
  1772.  
  1773.                         while ( enumerator.hasMoreElements() ) {
  1774.                             var win = enumerator.getNext();
  1775.  
  1776.                             if ( win.SimpleTimer.countdownTable.length !== 0 ) {
  1777.                                 allWinComplete = false;
  1778.                                 break;
  1779.                             }
  1780.                         }
  1781.  
  1782.                         if ( allWinComplete ) {
  1783.                             this.clock();
  1784.                         }
  1785.                     }
  1786.                     else {
  1787.                         this.changeBarMode("sleep");
  1788.                     }
  1789.                 }
  1790.                 else {
  1791.                     // Remove first element, and start the next one.
  1792.                     this.countdownTable.shift();
  1793.                     this.setCountdownTimer();
  1794.                 }
  1795.             }
  1796.         }
  1797.     },
  1798.  
  1799.     // Perform alerts if requested.
  1800.  
  1801.     checkUserPrefs: function(mode) {
  1802.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
  1803.                 getService(Components.interfaces.nsIWindowMediator);
  1804.         var win = wm.getMostRecentWindow("navigator:browser");
  1805.  
  1806.         // Compare window references.
  1807.         if ( win.window == window.window ) {
  1808.             // Only perform the alerts once.
  1809.             if ( this.audioNotify ) {
  1810.                 this.playSound(this.audioFile);
  1811.             }
  1812.  
  1813.             if ( this.popupNotify ) {
  1814.                 params = { displayItems: null, msg: this.getDisplayMsg(mode) };
  1815.                 SimpleTimerSliderAlert.addMessageToQueue(params);
  1816.             }
  1817.  
  1818.             if ( this.dialogNotify ) {
  1819.                 this.displayTimerCompleteDlg(mode);
  1820.             }
  1821.         }
  1822.     },
  1823.  
  1824.     // Play the audio file.
  1825.     // This can be performed on countdown or notify completion,
  1826.     // or if user clicks on audio icon in options dlg (possibly from Add-ons manager).
  1827.     // Also performed when user requests audio repeated.
  1828.     // Currently (1.7) not all .wav files supported by HTML5 audio, so use nsISound for them.
  1829.  
  1830.     playSound: function(audioFile) {
  1831.         var strbundle = document.getElementById("simtim-strings");
  1832.  
  1833.         // Do this because may be via Add-ons manager.
  1834.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
  1835.         var wmed = wm.QueryInterface(Components.interfaces.nsIWindowMediator);
  1836.         var enumerator = wmed.getEnumerator("navigator:browser");
  1837.  
  1838.         if ( enumerator.hasMoreElements() ) {
  1839.             var win = enumerator.getNext();
  1840.         }
  1841.  
  1842.         var selectedAudioFile;
  1843.  
  1844.         // Check which audio file user selected.
  1845.         switch ( audioFile ) {
  1846.             case 0:
  1847.                 selectedAudioFile = "alarm_clock";
  1848.                 break;
  1849.  
  1850.             case 1:
  1851.                 selectedAudioFile = "fanfare";
  1852.                 break;
  1853.  
  1854.             case 2:
  1855.                 selectedAudioFile = "custom";
  1856.                 break;
  1857.  
  1858.             case 3:
  1859.                 selectedAudioFile = "notify";
  1860.                 break;
  1861.  
  1862.             default:
  1863.                 selectedAudioFile = "alarm_clock";
  1864.         }
  1865.  
  1866.         if ( selectedAudioFile === "custom" && win.SimpleTimer.usingAudioObject ) {
  1867.             // .ogg. Bypass if already playing.
  1868.             // src set and loaded at startup, or when user sets file in Options.
  1869.             if ( win.SimpleTimer.audioObject.ended ||
  1870.                  win.SimpleTimer.audioObject.paused ||
  1871.                  win.SimpleTimer.audioObject.error ) {
  1872.                 try {
  1873.                     // Reset in case paused.
  1874.                     win.SimpleTimer.audioObject.currentTime = 0.0;
  1875.                     win.SimpleTimer.audioObject.play();
  1876.                     win.SimpleTimer.debug("Simple Timer: ogg playback started");
  1877.  
  1878.                     // Take care of context menu for each browser window.
  1879.                     win.document.getElementById("simtim-mitemKillHtmlAudio").disabled = false;
  1880.  
  1881.                     while ( enumerator.hasMoreElements() ) {
  1882.                         win = enumerator.getNext();
  1883.                         win.document.getElementById("simtim-mitemKillHtmlAudio").disabled = false;
  1884.                     }
  1885.                 }
  1886.                 catch(e) {
  1887.                     alert(strbundle.getString("alert.error.audio.failed") +
  1888.                             "\n" + e.message);
  1889.                 }
  1890.             }
  1891.         }
  1892.         else {
  1893.             // .wav. The nsISound play method takes a URI as an argument,
  1894.             // so first convert the URL to URI.
  1895.             var soundURL = ( selectedAudioFile === "custom" ) ?
  1896.                 "file:///" + win.SimpleTimer.customAudioPath :
  1897.                 "chrome://simpletimer/content/sounds/" +
  1898.                 selectedAudioFile + ".wav";
  1899.  
  1900.             try {
  1901.                 var sound = Components.classes["@mozilla.org/sound;1"].
  1902.                             createInstance(Components.interfaces.nsISound);
  1903.                 var ios = Components.classes["@mozilla.org/network/io-service;1"].
  1904.                             getService(Components.interfaces.nsIIOService);
  1905.                 var uri = ios.newURI(soundURL, null, null);
  1906.                 sound.init();
  1907.                 sound.play(uri);
  1908.             }
  1909.             catch (e) {
  1910.                 alert(strbundle.getString("alert.error.audio.failed") +
  1911.                        "\n" + e.name);
  1912.             }
  1913.         }
  1914.     },
  1915.  
  1916.     // Called when user closes Timer Completed dialog, or Kill Audio Alarm menuitem selected.
  1917.  
  1918.     pauseHtmlAudio: function() {
  1919.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
  1920.         var wmed = wm.QueryInterface(Components.interfaces.nsIWindowMediator);
  1921.         var enumerator = wmed.getEnumerator("navigator:browser");
  1922.  
  1923.         while ( enumerator.hasMoreElements() ) {
  1924.             var win = enumerator.getNext();
  1925.  
  1926.             // Seems odd there is no stop function.
  1927.             if ( win.SimpleTimer.audioObject && !win.SimpleTimer.audioObject.ended ) {
  1928.                 win.SimpleTimer.audioObject.pause();
  1929.                 win.document.getElementById("simtim-mitemKillHtmlAudio").disabled = true;
  1930.             }
  1931.         }
  1932.     },
  1933.  
  1934.     // Called when html5 audio file metadata has loaded.
  1935.     // this.audioDuration used when repeating audio alarm, see displayTimerCompleteDlg().
  1936.  
  1937.     audioLoadedMetaData: function(evt) {
  1938.         this.debug("Simple Timer: ogg metadata loaded");
  1939.         var audioObject = evt.target;
  1940.  
  1941.         this.audioDuration = audioObject.duration;
  1942.         this.debug("Simple Timer: ogg duration (secs): " + this.audioDuration);
  1943.     },
  1944.  
  1945.     // Called when html5 audio file playback ends.
  1946.  
  1947.     audioPlaybackEnded: function(evt) {
  1948.         this.debug("Simple Timer: ogg ended");
  1949.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
  1950.         var wmed = wm.QueryInterface(Components.interfaces.nsIWindowMediator);
  1951.         var enumerator = wmed.getEnumerator("navigator:browser");
  1952.  
  1953.         while ( enumerator.hasMoreElements() ) {
  1954.             var win = enumerator.getNext();
  1955.             win.document.getElementById("simtim-mitemKillHtmlAudio").disabled = true;
  1956.         }
  1957.     },
  1958.  
  1959.     // Called when html5 audio file playback paused.
  1960.     // Debugging.
  1961.  
  1962.     audioPlaybackPause: function(evt) {
  1963.         this.debug("Simple Timer: ogg paused");
  1964.     },
  1965.  
  1966.     // Called when html5 audio file playback aborts.
  1967.     // Debugging.
  1968.  
  1969.     audioPlaybackAbort: function(evt) {
  1970.         this.debug("Simple Timer: ogg aborted");
  1971.     },
  1972.  
  1973.     // Called when an error occurs during html5 audio file playback.
  1974.  
  1975.     audioPlaybackError: function(evt) {
  1976.         var strbundle = document.getElementById("simtim-strings");
  1977.         var audioObject = evt.target;
  1978.         var errorCode;
  1979.  
  1980.         switch ( audioObject.error.code ) {
  1981.             case 1:
  1982.                 errorCode = strbundle.getString("alert.error.html.error.aborted");
  1983.                 break;
  1984.  
  1985.             case 2:
  1986.                 errorCode = strbundle.getString("alert.error.html.error.network");
  1987.                 break;
  1988.  
  1989.             case 3:
  1990.                 errorCode = strbundle.getString("alert.error.html.error.decode");
  1991.                 break;
  1992.  
  1993.             case 4:
  1994.                 errorCode = strbundle.getString("alert.error.html.error.not.supported");
  1995.                 break;
  1996.  
  1997.             default:
  1998.                 errorCode = strbundle.getString("alert.error.html.error.unknown");
  1999.         }
  2000.  
  2001.         alert(strbundle.getString(errorCode));
  2002.     },
  2003.  
  2004.     // Display the timer completed dialog.
  2005.  
  2006.     displayTimerCompleteDlg: function(mode) {
  2007.         var strbundle = document.getElementById("simtim-strings");
  2008.  
  2009.         // Set the message to display in dialog.
  2010.         if ( this.audioNotify &&
  2011.              !this.repeatAudioId &&
  2012.              this.repeatMax > 0 &&
  2013.              this.repeatAudio !== 0 ) {
  2014.             // Only one audio timer no matter how many
  2015.             // dialogs stack up.
  2016.             // repeatAudio > 0 is minutes, < 0 is seconds.
  2017.             var repeatAudioInterval = this.repeatAudio > 0 ?
  2018.                     this.repeatAudio * 60 * 1000 :
  2019.                     this.repeatAudio * -1 * 1000;
  2020.  
  2021.             if ( this.usingAudioObject ) {
  2022.                 var audioDurationMilli = ( Math.ceil(this.audioDuration) + 1 ) * 1000;
  2023.  
  2024.                 if ( audioDurationMilli > repeatAudioInterval ) {
  2025.                     repeatAudioInterval = audioDurationMilli;
  2026.                 }
  2027.             }
  2028.  
  2029.             var repeatMaxInterval = this.repeatAudio > 0 ?
  2030.                     repeatAudioInterval * this.repeatMax + ( 30 * 1000 ) :
  2031.                     repeatAudioInterval * this.repeatMax + 500;
  2032.             this.repeatAudioId =
  2033.                     setInterval("SimpleTimer.playSound(SimpleTimer.audioFile);",
  2034.                     repeatAudioInterval);
  2035.             this.repeatMaxId =
  2036.                     setTimeout("SimpleTimer.clearTimerCompleteInterval();",
  2037.                     repeatMaxInterval);
  2038.         }
  2039.  
  2040.         // It seems odd that the dlgs directly overlap with
  2041.         // no offset. So manually set offset.
  2042.         try {
  2043.             var wm = Components.classes['@mozilla.org/appshell/window-mediator;1'].
  2044.                     getService(Components.interfaces.nsIWindowMediator);
  2045.             var enumerator = wm.getEnumerator("dialog:timerComplete");
  2046.  
  2047.             while(enumerator.hasMoreElements()) {
  2048.                 var win = enumerator.getNext();
  2049.                 win.moveBy(-26, -26);
  2050.             }
  2051.         }
  2052.         catch (e) {
  2053.             alert(strbundle.getString("alert.error.dialog.failed") +
  2054.                   "\n" + e.name);
  2055.         }
  2056.  
  2057.         var msg = this.getDisplayMsg(mode);
  2058.         var url = this.getDisplayUrl(mode);
  2059.         var params = {inn: {msg: msg, url: url}, out: null};
  2060.  
  2061.         // Non-modal.
  2062.         window.openDialog(
  2063.                 "chrome://simpletimer/content/simpletimer-timerComplete.xul",
  2064.                 "",
  2065.                 "centerscreen, chrome, resizable, dependent",
  2066.                 params);
  2067.     },
  2068.  
  2069.     // Called when user closes Timer Completed dialog,
  2070.     // or when max repeat audio cycles reached.
  2071.  
  2072.     clearTimerCompleteInterval: function() {
  2073.         if ( this.repeatAudioId ) {
  2074.             clearInterval(this.repeatAudioId);
  2075.             this.repeatAudioId = 0;
  2076.         }
  2077.  
  2078.         if ( this.repeatMaxId ) {
  2079.             clearTimeout(this.repeatMaxId);
  2080.             this.repeatMaxId = 0;
  2081.         }
  2082.     },
  2083.  
  2084.     // Get the message to be displayed.
  2085.  
  2086.     getDisplayMsg: function(mode) {
  2087.         var strbundle = document.getElementById("simtim-strings");
  2088.  
  2089.         // If user supplied a description, use it.
  2090.         if ( mode === "notify" ) {
  2091.             return this.notifyTable[this.currNotifyIndex].description ?
  2092.                    this.notifyTable[this.currNotifyIndex].description :
  2093.                    strbundle.getString("alert.notification.notify.complete");
  2094.         }
  2095.         else {
  2096.             return this.countdownTable[mode].description ?
  2097.                    this.countdownTable[mode].description :
  2098.                    strbundle.getString("alert.notification.timer.complete");
  2099.         }
  2100.     },
  2101.  
  2102.     // Get the URL, if any, to be displayed.
  2103.  
  2104.     getDisplayUrl: function(mode) {
  2105.         if ( mode === "notify" ) {
  2106.             return this.notifyTable[this.currNotifyIndex].url ?
  2107.                     this.notifyTable[this.currNotifyIndex].url : "";
  2108.         }
  2109.         else {
  2110.             return "";
  2111.         }
  2112.     },
  2113.  
  2114.     // Called when user selects Count Up menuitem.
  2115.  
  2116.     countUp: function() {
  2117.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
  2118.         var wmed = wm.QueryInterface(Components.interfaces.nsIWindowMediator);
  2119.         var enumerator = wmed.getEnumerator("navigator:browser");
  2120.  
  2121.         while ( enumerator.hasMoreElements() ) {
  2122.             var win = enumerator.getNext();
  2123.  
  2124.             win.SimpleTimer.cancelTimerInProgress();
  2125.  
  2126.             if ( win.SimpleTimer.countdowns ) {
  2127.                 win.SimpleTimer.countdownTable = [];
  2128.                 win.SimpleTimer.setCountdownTable();
  2129.                 win.SimpleTimerCountdown.updateCountdownPref();
  2130.             }
  2131.  
  2132.             win.SimpleTimer.changeBarMode("wake");
  2133.             win.SimpleTimer.displayTime(0, 0, 0);
  2134.             win.SimpleTimer.startTime = new Date();
  2135.             win.SimpleTimer.countupInProgress = true;
  2136.  
  2137.             win.SimpleTimer.timerID = win.setInterval(win.SimpleTimer.increaseTimer, 1000);
  2138.         }
  2139.     },
  2140.  
  2141.     // Increment and display the timer.
  2142.  
  2143.     increaseTimer: function() {
  2144.         SimpleTimer.calcElapsedTime();
  2145.         SimpleTimer.displayTime(SimpleTimer.elapsedHours, SimpleTimer.elapsedMins, SimpleTimer.elapsedSecs);
  2146.     },
  2147.  
  2148.     // Called when user selects Pause/Resume menuitem, or via middle-click on panel.
  2149.  
  2150.     pauseResume: function() {
  2151.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
  2152.         var wmed = wm.QueryInterface(Components.interfaces.nsIWindowMediator);
  2153.         var enumerator = wmed.getEnumerator("navigator:browser");
  2154.  
  2155.         if ( this.countdownInProgress || this.countupInProgress ) {
  2156.             while ( enumerator.hasMoreElements() ) {
  2157.                 var win = enumerator.getNext();
  2158.                 if ( win.SimpleTimer.countupInProgress || win.SimpleTimer.countdownInProgress ) {
  2159.                     if ( win.SimpleTimer.paused ) {
  2160.                         win.SimpleTimer.resume();
  2161.                     }
  2162.                     else {
  2163.                         win.SimpleTimer.pause();
  2164.                     }
  2165.                 }
  2166.             }
  2167.         }
  2168.     },
  2169.  
  2170.      // Stop the current count up/count down, leave the bar display intact.
  2171.  
  2172.     pause: function() {
  2173.         if ( SimpleTimer.countupInProgress ) {
  2174.             clearInterval(SimpleTimer.timerID);
  2175.             SimpleTimer.timerID = 0;
  2176.             SimpleTimer.startTime = null;
  2177.         }
  2178.         else {
  2179.             // Reset timeMilli to the remaining time. When resumed, it will be
  2180.             // processed as if a new timer (although the display time is unchanged).
  2181.             SimpleTimer.countdownTable[0].timeMilli = SimpleTimer.remainingMilli;
  2182.             SimpleTimer.countdownTable[0].timeFinishedMilli = 0;
  2183.             SimpleTimer.countdownTable[0].paused = true;
  2184.  
  2185.             // Update countdowns pref.
  2186.             SimpleTimer.setCountdownTable();
  2187.             SimpleTimerCountdown.updateCountdownPref();
  2188.         }
  2189.  
  2190.         SimpleTimer.paused = true;
  2191.     },
  2192.  
  2193.     // Resume the current count up/count down.
  2194.  
  2195.     resume: function() {
  2196.         SimpleTimer.paused = false;
  2197.  
  2198.         if ( SimpleTimer.countupInProgress ) {
  2199.             SimpleTimer.startTime = new Date(new Date().getTime() - SimpleTimer.elapsedMilli);
  2200.             SimpleTimer.timerID = setInterval("SimpleTimer.increaseTimer()", 1000);
  2201.         }
  2202.         else {
  2203.             this.cancelTimerInProgress();
  2204.  
  2205.             SimpleTimer.countdownMilli = SimpleTimer.countdownTable[0].timeMilli;
  2206.             SimpleTimer.countdownTable[0].paused = false;
  2207.  
  2208.             // Countdowns pref.updated there.
  2209.             SimpleTimer.setCountdownTimer();
  2210.         }
  2211.     },
  2212.  
  2213.     // Called when user selects Stop/Reset menuitem, or via double-click on panel.
  2214.  
  2215.     stop: function() {
  2216.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
  2217.         var wmed = wm.QueryInterface(Components.interfaces.nsIWindowMediator);
  2218.         var enumerator = wmed.getEnumerator("navigator:browser");
  2219.  
  2220.         while ( enumerator.hasMoreElements() ) {
  2221.             var win = enumerator.getNext();
  2222.  
  2223.             win.SimpleTimer.cancelTimerInProgress();
  2224.  
  2225.             if ( win.SimpleTimer.countdownTable.length > 0 ) {
  2226.                 win.SimpleTimer.countdownTable.shift();
  2227.                 win.SimpleTimer.setCountdownTimer();
  2228.             }
  2229.             else {
  2230.                win.SimpleTimer.changeBarMode("sleep");
  2231.             }
  2232.         }
  2233.     },
  2234.  
  2235.     // Displays the options dialog.
  2236.  
  2237.     displayOptionsDlg: function() {
  2238.         window.openDialog(
  2239.                 "chrome://simpletimer/content/simpletimer-options.xul",
  2240.                 "",
  2241.                 "centerscreen, toolbar, titlebar, chrome, resizable, dependent");
  2242.     },
  2243.  
  2244.     // Displays the Event Log dialog.
  2245.  
  2246.     displayEventLogDlg: function() {
  2247.         window.openDialog(
  2248.                 "chrome://simpletimer/content/simpletimer-eventLog.xul",
  2249.                 "",
  2250.                 "centerscreen, chrome, resizable, dependent");
  2251.     },
  2252.  
  2253.     // Displays the About dialog.
  2254.  
  2255.     displayAboutDlg: function() {
  2256.         window.openDialog(
  2257.                 "chrome://simpletimer/content/simpletimer-about.xul",
  2258.                 "",
  2259.                 "centerscreen, chrome, resizable, dependent");
  2260.     },
  2261.  
  2262.     // Generic open page in new tab.
  2263.     // This function developed by Devon Jensen, from Download Statusbar.
  2264.  
  2265.     openLink: function(aPage) {
  2266.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
  2267.                 getService();
  2268.         var wmed = wm.QueryInterface(Components.interfaces.nsIWindowMediator);
  2269.         var win = wmed.getMostRecentWindow("navigator:browser");
  2270.  
  2271.         if ( !win ) {
  2272.             win = window.openDialog(
  2273.                     "chrome://browser/content/browser.xul",
  2274.                     "_blank",
  2275.                     "chrome,all,dialog=no",
  2276.                     aPage, null, null);
  2277.         }
  2278.         else {
  2279.             var content = win.document.getElementById("content");
  2280.             content.selectedTab = content.addTab(aPage);
  2281.         }
  2282.     },
  2283.  
  2284.     // Open page in new tab.
  2285.     // Use this to auto-open (completed notification) a page for each browser window open.
  2286.  
  2287.     autoOpenLink: function(aPage) {
  2288.         var content = document.getElementById("content");
  2289.         content.selectedTab = content.addTab(aPage);
  2290.     },
  2291.  
  2292.     // Check if other browser windows open.
  2293.     // Don't display the startup slider alerts if true.
  2294.  
  2295.     otherBrowserWindowOpen: function() {
  2296.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
  2297.         var wmed = wm.QueryInterface(Components.interfaces.nsIWindowMediator);
  2298.         var enumerator = wmed.getEnumerator("navigator:browser");
  2299.  
  2300.         if ( enumerator.hasMoreElements() ) {
  2301.             // Skip oldest window.
  2302.             enumerator.getNext();
  2303.         }
  2304.  
  2305.         return enumerator.hasMoreElements();
  2306.     },
  2307.  
  2308.     // If other browser windows open, check if countup in progress.
  2309.     // If so, mirror it.
  2310.  
  2311.     countupInOtherBrowserWindow: function() {
  2312.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
  2313.         var wmed = wm.QueryInterface(Components.interfaces.nsIWindowMediator);
  2314.         var enumerator = wmed.getEnumerator("navigator:browser");
  2315.  
  2316.         if ( enumerator.hasMoreElements() ) {
  2317.             // Oldest first.
  2318.             var win = enumerator.getNext();
  2319.  
  2320.             if ( win.SimpleTimer.countupInProgress ) {
  2321.                 this.countupInProgress = true;
  2322.                 this.paused =  win.SimpleTimer.paused;
  2323.                 this.changeBarMode("wake");
  2324.  
  2325.                 if ( this.paused ) {
  2326.                     this.elapsedMilli = win.SimpleTimer.elapsedMilli;
  2327.                     this.elapsedHours = win.SimpleTimer.elapsedHours;
  2328.                     this.elapsedMins = win.SimpleTimer.elapsedMins;
  2329.                     this.elapsedSecs = win.SimpleTimer.elapsedSecs;
  2330.                     this.displayTime(this.elapsedHours, this.elapsedMins, this.elapsedSecs);
  2331.                 }
  2332.                 else {
  2333.                     this.startTime =  win.SimpleTimer.startTime;
  2334.                     this.timerID = window.setInterval(window.SimpleTimer.increaseTimer, 1000);
  2335.                 }
  2336.  
  2337.                 return true;
  2338.             }
  2339.         }
  2340.  
  2341.         return false;
  2342.     },
  2343.  
  2344.     // Deep copy an array of objects.
  2345.  
  2346.     deepCopy: function(inArray) {
  2347.         var json = Components.classes["@mozilla.org/dom/json;1"].
  2348.                 createInstance(Components.interfaces.nsIJSON);
  2349.         var outArray = [];
  2350.  
  2351.         for ( var i in inArray ) {
  2352.             outArray.push(json.decode(json.encode(inArray[i])));
  2353.         }
  2354.  
  2355.         return outArray;
  2356.     },
  2357.  
  2358.     // Dump to console.
  2359.  
  2360.     dumpNotificationObject: function(notification) {
  2361.         this.debug("\nNotification data:");
  2362.         this.debug("Completed?: " + notification.completed);
  2363.         this.debug("Recurring?: " + notification.recurring);
  2364.         this.debug("Notification time: " + notification.timeDisplay);
  2365.         this.debug("Description: " + notification.description);
  2366.         this.debug("Notification time (24 hr format): " + notification.time24hr);
  2367.         this.debug("Notification time (millisecs): " + notification.timeMilli);
  2368.         this.debug("Notification day: " + notification.day);
  2369.         this.debug("Url: " + notification.url);
  2370.     },
  2371.  
  2372.     // Dump to console.
  2373.  
  2374.     dumpCountdownObject: function(countdownTimer) {
  2375.         this.debug("\nCountdown timer data:");
  2376.         this.debug("Countdown Time for Display: " + countdownTimer.timeDisplay);
  2377.         this.debug("Recurring?: " + countdownTimer.recurring);
  2378.         this.debug("Description: " + countdownTimer.description);
  2379.         this.debug("Countdown time (millisecs): " + countdownTimer.timeMilli);
  2380.         this.debug("Countdown finish time (millisecs): " + countdownTimer.timeFinishedMilli);
  2381.     },
  2382.  
  2383.     // Debug messages to console.
  2384.  
  2385.     debug: function(aMsg) {
  2386.         setTimeout(function() { throw new Error("[debug] " + aMsg); }, 0);
  2387.     }
  2388. };
  2389.